package ace.cmp.spring.graphql.core.helper.impl;

import ace.cmp.spring.graphql.core.helper.TypeDefinitionRegistryHelper;
import graphql.language.*;
import graphql.schema.idl.TypeDefinitionRegistry;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.collections.MapUtils;

/**
 * @author caspar
 * @date 2023/8/26 10:00
 */
public class TypeDefinitionRegistryHelperImpl implements TypeDefinitionRegistryHelper {

  @Override
  public void injectObjectTypeDefinition(
      TypeDefinitionRegistry registry, ObjectTypeDefinition injectObjectTypeDefinition) {
    Optional<TypeDefinition> existTypeDefinitionOptional =
        registry.getType(injectObjectTypeDefinition.getName());
    if (existTypeDefinitionOptional.isEmpty()) {
      registry.add(injectObjectTypeDefinition);
    } else {
      if (!(existTypeDefinitionOptional.get() instanceof ObjectTypeDefinition)) {
        throw new RuntimeException(
            String.format(
                "type[%s] exist another type definition is not ObjectTypeDefinition.",
                injectObjectTypeDefinition.getName()));
      }
      ObjectTypeDefinition existObjectTypeDefinition =
          (ObjectTypeDefinition) existTypeDefinitionOptional.get();
      ObjectTypeDefinition newObjectTypeDefinition =
          existObjectTypeDefinition.transform(
              existBuilder -> {
                if (CollectionUtils.isNotEmpty(injectObjectTypeDefinition.getFieldDefinitions())) {
                  List<FieldDefinition> fieldDefinitions =
                      injectObjectTypeDefinition.getFieldDefinitions().stream()
                          .filter(
                              p ->
                                  existObjectTypeDefinition.getFieldDefinitions().stream()
                                      .noneMatch(p2 -> p2.getName().equals(p.getName())))
                          .collect(Collectors.toList());
                  if (!CollectionUtils.isEmpty(fieldDefinitions)) {
                    existBuilder.fieldDefinitions(
                        ListUtils.union(
                            existObjectTypeDefinition.getFieldDefinitions(), fieldDefinitions));
                  }
                }
                if (CollectionUtils.isNotEmpty(injectObjectTypeDefinition.getComments())) {
                  existBuilder.comments(
                      ListUtils.union(
                          existObjectTypeDefinition.getComments(),
                          injectObjectTypeDefinition.getComments()));
                }
                if (MapUtils.isNotEmpty(injectObjectTypeDefinition.getAdditionalData())) {
                  Map<String, String> additionalData = new HashMap<>();
                  additionalData.putAll(existObjectTypeDefinition.getAdditionalData());
                  additionalData.putAll(injectObjectTypeDefinition.getAdditionalData());
                  existBuilder.additionalData(additionalData);
                }
                if (CollectionUtils.isNotEmpty(injectObjectTypeDefinition.getDirectives())) {
                  List<Directive> needAddDirectives =
                      injectObjectTypeDefinition.getDirectives().stream()
                          .filter(
                              p ->
                                  existObjectTypeDefinition.getDirectives().stream()
                                      .noneMatch(p2 -> p2.getName().equals(p.getName())))
                          .collect(Collectors.toList());
                  existBuilder.comments(
                      ListUtils.union(
                          existObjectTypeDefinition.getDirectives(), needAddDirectives));
                }
                if (CollectionUtils.isNotEmpty(injectObjectTypeDefinition.getImplements())) {
                  existBuilder.implementz(
                      ListUtils.union(
                          existObjectTypeDefinition.getImplements(),
                          injectObjectTypeDefinition.getImplements()));
                }
                if (existObjectTypeDefinition.getDescription() == null
                    && injectObjectTypeDefinition.getDescription() != null) {
                  existBuilder.description(injectObjectTypeDefinition.getDescription());
                }
                if (existObjectTypeDefinition.getIgnoredChars() == null
                    && injectObjectTypeDefinition.getIgnoredChars() != null) {
                  existBuilder.ignoredChars(injectObjectTypeDefinition.getIgnoredChars());
                }
                if (existObjectTypeDefinition.getSourceLocation() == null
                    && injectObjectTypeDefinition.getSourceLocation() != null) {
                  existBuilder.sourceLocation(injectObjectTypeDefinition.getSourceLocation());
                }
              });

      registry.remove(existObjectTypeDefinition);
      registry.add(newObjectTypeDefinition);
    }
  }

  @Override
  public void injectToAssignTypeField(
      TypeDefinitionRegistry registry,
      String typeName,
      String fieldOfType,
      Type injectTypeOfField) {
    Optional<TypeDefinition> existTypeDefinitionOptional = registry.getType(typeName);
    FieldDefinition injectFieldDefinition =
        FieldDefinition.newFieldDefinition().name(fieldOfType).type(injectTypeOfField).build();
    if (existTypeDefinitionOptional.isPresent()) {
      if (!(existTypeDefinitionOptional.get() instanceof ObjectTypeDefinition)) {
        throw new RuntimeException(
            String.format(
                "type[%s] exist another type definition is not ObjectTypeDefinition type.",
                typeName));
      }
      ObjectTypeDefinition existObjectTypeDefinition =
          (ObjectTypeDefinition) existTypeDefinitionOptional.get();
      boolean existField =
          existObjectTypeDefinition.getFieldDefinitions().stream()
              .anyMatch(p -> p.getName().equals(fieldOfType));
      if (existField) {
        return;
      }
      ObjectTypeDefinition newObjectTypeDefinition =
          existObjectTypeDefinition.transform(
              existBuilder -> {
                existBuilder.fieldDefinition(injectFieldDefinition);
              });
      registry.remove(existObjectTypeDefinition);
      registry.add(newObjectTypeDefinition);
    } else {
      ObjectTypeDefinition newObjectTypeDefinition =
          ObjectTypeDefinition.newObjectTypeDefinition()
              .name(typeName)
              .fieldDefinition(injectFieldDefinition)
              .build();

      registry.add(newObjectTypeDefinition);
    }
  }
}
